#include <libwebsockets.h>
#include <signal.h>
#include "cJSON.h"
#include <pthread.h>

#define MAX_PAYLOAD_SIZE  50 * 1024  //如果接收的数据很多时，可以把这设置大，比如音频以base64回过来的时候，这儿设置500*1024，才可能能一次性接收，不然会分好多次接收，会导致解析麻烦

static struct lws_context_creation_info ctx_info = { 0 };
static struct lws_context *context = NULL;
static struct lws_client_connect_info conn_info = { 0 };
static struct lws *wsi = NULL;

//创建客户端对象（传入服务器地址和端口）
char *server_address = "ip.xiaoice.com";
int port = 80;

static int interrupted;

void sigint_handler(int sig)
{
	interrupted = 1;
}

/* 设置ssl */
int set_ssl(const char* ca_filepath, 
                            const char* server_cert_filepath,
                            const char*server_private_key_filepath,
                            _Bool is_support_ssl)
{
    if(!is_support_ssl)
    {
        ctx_info.ssl_ca_filepath = NULL;
        ctx_info.ssl_cert_filepath = NULL;
        ctx_info.ssl_private_key_filepath = NULL;
    }
    else
    {
        ctx_info.ssl_ca_filepath = ca_filepath;
        ctx_info.ssl_cert_filepath = server_cert_filepath;
        ctx_info.ssl_private_key_filepath = server_private_key_filepath;
        ctx_info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
    //ctx_info.options |= LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT;
    }

    return is_support_ssl;
}

/**
 * 会话上下文对象，结构根据需要自定义
 */
struct session_data {
    int msg_count;
    unsigned char buf[LWS_PRE + MAX_PAYLOAD_SIZE];
    size_t len;
};

struct session_data *session_data_buf = NULL;


char* send_audio_command() {
    printf("send_audio_command\n");

    cJSON *jsonContentObject = cJSON_CreateObject();
    cJSON_AddItemToObject(jsonContentObject, "productId", cJSON_CreateString("66eaa703b3c870e931262415"));
    cJSON_AddItemToObject(jsonContentObject, "deviceId", cJSON_CreateString("AF224293PSB98EA"));
    cJSON_AddItemToObject(jsonContentObject, "deviceSecret", cJSON_CreateString("3rxFIBhd7Rr-QA6jYMg2I4QLbjplU_kI"));

    cJSON *jsonTtsConfigObject = cJSON_CreateObject();
    //cJSON_AddItemToObject(jsonTtsConfigObject, "outputType", cJSON_CreateString("AUDIO_DATA"));  //AUDIO_DATA回过来的音频会以base64编码的二进制发送过来
    cJSON_AddItemToObject(jsonTtsConfigObject, "outputType", cJSON_CreateString("OSS_LINK")); //OSS_LINK回过来的是一个audio url
    cJSON_AddItemToObject(jsonTtsConfigObject, "audioFormat", cJSON_CreateString("WAV"));


    cJSON *jsonHeaderObject = cJSON_CreateObject();
    cJSON_AddItemToObject(jsonHeaderObject, "name", cJSON_CreateString("chat.audio_in"));
    cJSON_AddItemToObject(jsonHeaderObject, "requestId", cJSON_CreateString("1"));
    cJSON_AddItemToObject(jsonHeaderObject, "ttsConfig", jsonTtsConfigObject);



    cJSON *jsonPayloadObject = cJSON_CreateObject();
    // cJSON_AddItemToObject(jsonPayloadObject, "audioFormat", cJSON_CreateString("WAV"));
    cJSON_AddItemToObject(jsonPayloadObject, "audioFormat", cJSON_CreateString("WAV"));
    cJSON_AddItemToObject(jsonPayloadObject, "sampleRate", cJSON_CreateString("16k"));

    // cJSON_AddItemToObject(jsonObject, "wife", wife);

    cJSON *jsonObject = cJSON_CreateObject();
    cJSON_AddItemToObject(jsonObject, "context", jsonContentObject);
    cJSON_AddItemToObject(jsonObject, "header", jsonHeaderObject);
    cJSON_AddItemToObject(jsonObject, "payload", jsonPayloadObject);

    char *content = cJSON_Print(jsonObject);
    //printf("send_audio_command: %s\n",  content);
    return content;
}

char* send_text_command() {
    printf("send_audio_command\n");

    cJSON *jsonContentObject = cJSON_CreateObject();
    cJSON_AddItemToObject(jsonContentObject, "productId", cJSON_CreateString("66eaa703b3c870e931262415"));
    cJSON_AddItemToObject(jsonContentObject, "deviceId", cJSON_CreateString("AF224293PSB98EA"));
    cJSON_AddItemToObject(jsonContentObject, "deviceSecret", cJSON_CreateString("3rxFIBhd7Rr-QA6jYMg2I4QLbjplU_kI"));


    //header
    cJSON *jsonTtsConfigObject = cJSON_CreateObject();
    //cJSON_AddItemToObject(jsonTtsConfigObject, "outputType", cJSON_CreateString("AUDIO_DATA"));  //AUDIO_DATA回过来的音频会以base64编码的二进制发送过来
    cJSON_AddItemToObject(jsonTtsConfigObject, "outputType", cJSON_CreateString("OSS_LINK")); //OSS_LINK回过来的是一个audio url

    cJSON *jsonHeaderObject = cJSON_CreateObject();
    cJSON_AddItemToObject(jsonHeaderObject, "name", cJSON_CreateString("chat.text_in"));
    cJSON_AddItemToObject(jsonHeaderObject, "requestId", cJSON_CreateString("1"));
    cJSON_AddItemToObject(jsonHeaderObject, "ttsConfig", jsonTtsConfigObject);


    cJSON *jsonPayloadObject = cJSON_CreateObject();
    cJSON_AddItemToObject(jsonPayloadObject, "text", cJSON_CreateString("你猜我想问啥"));


    cJSON *jsonObject = cJSON_CreateObject();
    cJSON_AddItemToObject(jsonObject, "context", jsonContentObject);
    cJSON_AddItemToObject(jsonObject, "header", jsonHeaderObject);
    cJSON_AddItemToObject(jsonObject, "payload", jsonPayloadObject);

    char *content = cJSON_Print(jsonObject);
    //printf("send_audio_command: %s\n",  content);
    return content;
}


void parseAudioTTSResponse(char *audio_response) {
    // 第一步：解析json 字符串
    cJSON *jsonObject = cJSON_Parse(audio_response);
    if(jsonObject == NULL){
        perror("Parse failed!\n");
        return;
    }

    // 第二步：解析header键值对
    cJSON *headerJson =cJSON_GetObjectItem(jsonObject, "header");
    char *content = cJSON_Print(headerJson);
    //printf("%s\n",  content);

    cJSON *headerNameJson =cJSON_GetObjectItem(headerJson, "name");
    content = cJSON_Print(headerNameJson);
    printf("headerName: %s\n",  content);

    cJSON *headerRequestIdJson =cJSON_GetObjectItem(headerJson, "requestId");
    content = cJSON_Print(headerRequestIdJson);
    printf("headerRequestId: %s\n",  content);


    //解析payload键值对
    cJSON *payloadJson =cJSON_GetObjectItem(jsonObject, "payload");
    if (payloadJson) {
        //content = cJSON_Print(payloadJson);
        //printf("%s\n",  content);

        cJSON *payloadTextJson =cJSON_GetObjectItem(payloadJson, "text");
        if (payloadTextJson) {
            content = cJSON_Print(payloadTextJson);
            printf("payloadTextJson: %s\n",  content);
        }


        cJSON *payloadAudioJson =cJSON_GetObjectItem(payloadJson, "audio");
        // if (payloadAudioJson) {
        //     content = cJSON_Print(payloadAudioJson);
        //     printf("payloadAudioJson: %s\n",  content);
        // }

        cJSON *payloadAudioUrlJson =cJSON_GetObjectItem(payloadAudioJson, "audioUrl");
        if (payloadAudioUrlJson) {
            content = cJSON_Print(payloadAudioUrlJson);
            printf("payloadAudioUrlJson: %s\n",  content);
        }

        cJSON *payloadAudioFormatJson =cJSON_GetObjectItem(payloadAudioJson, "audioFormat");
        if (payloadAudioFormatJson) {
            content = cJSON_Print(payloadAudioFormatJson);
            printf("payloadAudioFormatJson: %s\n",  content);
        }

        cJSON *payloadAudioSampleRateJson =cJSON_GetObjectItem(payloadAudioJson, "sampleRate");
        if (payloadAudioSampleRateJson) {
            content = cJSON_Print(payloadAudioSampleRateJson);
            printf("payloadAudioSampleRateJson: %s\n",  content);
        }
    }
}

/**
 * 某个协议下的连接发生事件时，执行的回调函数
 *
 * wsi：指向WebSocket实例的指针
 * reason：导致回调的事件
 * user 库为每个WebSocket会话分配的内存空间
 * in 某些事件使用此参数，作为传入数据的指针
 * len 某些事件使用此参数，说明传入数据的长度
 */
int lws_client_writeable = 0;
int lws_client_callback( struct lws *wsi, 
                enum lws_callback_reasons reason, 
                void *user, 
                void *in, 
                size_t len )
{
    //session_data_buf = (struct session_data *) user;

    switch ( reason ) {

        case LWS_CALLBACK_CLIENT_ESTABLISHED:   // 连接到服务器后的回调
            lwsl_notice( "Connected to server ok!\n" );
            break;
 
        case LWS_CALLBACK_CLIENT_RECEIVE:       // 接收到服务器数据后的回调，数据为in，其长度为len
            lwsl_notice("len = %d\n", (int)len);
            //lwsl_notice( "RxLength: %d,  Rx: %s\n", (int)strlen((char *)in), (char *) in );
            printf( "RxLength: %d,  Rx: %s\n", (int)strlen((char *)in), (char *) in );
            parseAudioTTSResponse((char *)in);
            break;

        case LWS_CALLBACK_CLIENT_WRITEABLE:     // 当此客户端可以发送数据时的回调
            // if ( session_data_buf->msg_count < 1 ) {
            //     // char *json_send = send_text_command();
            //     char *json_send = send_audio_command();
            //     memset( session_data_buf->buf, 0, sizeof( session_data_buf->buf ));
            //     char *msg = (char *) &session_data_buf->buf[ LWS_PRE ];
            //     session_data_buf->len = (size_t)sprintf( msg, "%s", json_send);
            //     //lwsl_notice( "Tx: %s\n", msg );
            //     printf("msg length: %d\n",  (int)session_data_buf->len);
            //     printf("send_audio_command: %s\n",  msg);
            //     // 通过WebSocket发送文本消息
            //     lws_write( wsi, &session_data_buf->buf[ LWS_PRE ], session_data_buf->len, LWS_WRITE_TEXT );
            //     session_data_buf->msg_count++;
            //     //send_audio(wsi, data);
            //     printf("LWS_CALLBACK_CLIENT_WRITEABLE..................\n");
            // }
            //printf("LWS_CALLBACK_CLIENT_WRITEABLE..................\n");
            //if (lws_client_writeable == 0) {
                session_data_buf = (struct session_data *) user;
                //printf("session_data_buf address = %p\n", session_data_buf);
            //}

            lws_client_writeable = 1;
            break;

        default:
            break;
//            lwsl_notice("not support\n");
    }

    return 0;
}

/**
 * 支持的WebSocket子协议数组
 * 子协议即JavaScript客户端WebSocket(url, protocols)第2参数数组的元素
 * 你需要为每种协议提供回调函数
 */
struct lws_protocols protocols[] = {
    {
        //协议名称，协议回调，接收缓冲区大小
        "ws", lws_client_callback, sizeof( struct session_data ), MAX_PAYLOAD_SIZE, 0, NULL, 0
    },
    {
		// 最后一个元素固定为此格式
        NULL, NULL, 0, 0, 0, NULL, 0
    }
};

/* 连接客户端 */
int connect_server(int is_ssl_support)
{
    char addr_port[256] = { 0 };
    sprintf(addr_port, "%s:%u", server_address, port & 65535 );
 
    // 客户端连接参数
    //conn_info = { 0 };
    conn_info.context = context;
    conn_info.address = server_address;
    conn_info.port = port;

    if(!is_ssl_support)
        conn_info.ssl_connection = 0;
    else
        conn_info.ssl_connection = 1;
    conn_info.path = "./openapi/v1/iot-ws/characters/cjfx-ledi/sessions/test123";
    //conn_info.path = "./openapi/v1/iot/device/activate";
    conn_info.host = addr_port;
    conn_info.origin = addr_port;
    conn_info.protocol = protocols[ 0 ].name;
 
    // 下面的调用触发LWS_CALLBACK_PROTOCOL_INIT事件
    // 创建一个客户端连接
    wsi = lws_client_connect_via_info( &conn_info );
    if(!wsi)
        return -1;
    return 1;
}

/*
运行客户端
*/
int run_loop(int wait_time)
{
    lws_service( context, wait_time );
    /**
     * 下面的调用的意义是：当连接可以接受新数据时，触发一次WRITEABLE事件回调
     * 当连接正在后台发送数据时，它不能接受新的数据写入请求，所有WRITEABLE事件回调不会执行
     */
    int ret = lws_callback_on_writable( wsi );
	if (ret < 0) {
		lwsl_notice( "run_loop: ret = %d\n", ret);
	}

	return ret;
}


int audio_command = 0;
void send_audio(struct session_data *session_data) {
    FILE * file = NULL;
    // char audio_path[64] = "./test_audio.mp3";
    char audio_path[64] = "./test_S16LE_C1.wav";
    size_t fileSize = 0;
    size_t readLen = 0;
    char audio_data[1024] = {0};

    file = fopen(audio_path,"rb");
    if(file == NULL) {
        printf("fopen failed\n");
        return;
    }
    fseek(file,0,SEEK_END);
    fileSize = (size_t)ftell(file);
    fseek(file,0,SEEK_SET);

    printf("audio fileSize = %d\n", (int)fileSize);
    while (fileSize > 0) {
        if (!lws_client_writeable) {
            continue;
        }

        readLen = fread(audio_data,1,sizeof(audio_data),file);

        memcpy(&session_data->buf[ LWS_PRE ], audio_data, readLen);
        lws_write( wsi, &session_data->buf[ LWS_PRE ], readLen, LWS_WRITE_BINARY );
        lws_client_writeable = 0;

       fileSize -= readLen;
    }
    printf("audio send complete...");

    while(1) {
        if (!lws_client_writeable) {
            continue;
        }

        char audio_end[36] = "__AUDIO_END__";
        int audio_end_len = sprintf( (char*)(session_data->buf + LWS_PRE), "%s", audio_end);
        lws_write( wsi, &session_data->buf[ LWS_PRE ], (size_t)audio_end_len, LWS_WRITE_TEXT );
        break;
    }
}



void *send_audio_thr_fn(void *arg){
    printf("send_audio_thr_fn\n");
    // char *json_send = send_text_command();
    char *json_send = send_audio_command();

	while (1) {
        if (lws_client_writeable && audio_command == 0) {
            printf("send_audio_thr_fn lws_client_writeable, can send data..\n");
            memset( session_data_buf->buf, 0, sizeof( session_data_buf->buf ));
            char *msg = (char *) &session_data_buf->buf[ LWS_PRE ];
            session_data_buf->len = (size_t)sprintf( msg, "%s", json_send);
            // //lwsl_notice( "Tx: %s\n", msg );
             printf("msg length: %d\n",  (int)session_data_buf->len);
             printf("send_audio_command: %s\n",  msg);
            // // 通过WebSocket发送文本消息
             lws_write( wsi, &session_data_buf->buf[ LWS_PRE ], session_data_buf->len, LWS_WRITE_TEXT );
             lws_client_writeable = 0;
             audio_command = 1;
            //return ((void *)0);
        }

        if (audio_command == 1) {
            printf("send_audio_thr_fn, send audio binary\n");
            send_audio(session_data_buf);
            return ((void *)0);
        }

        printf("send_audio_thr_fn sleep...\n");
        usleep(2000 * 1000);
    }
}


int main(int argc,char **argv)
{
    signal(SIGINT, sigint_handler);
    int n = 0;
    pthread_t ntid;
    int		err;

    // 第一步：创建cJSON 对象
    cJSON *jsonObject = cJSON_CreateObject();
    // 第二步：输出cJSON 对象
    char *content = cJSON_Print(jsonObject);
    printf("%s\n",  content);
        // 第三步：cJSON 对象添加属性:基本属性之字符串
    cJSON_AddItemToObject(jsonObject, "name", cJSON_CreateString("周志刚"));
    content = cJSON_Print(jsonObject);
    printf("%s\n",  content);
     // 第三步：cJSON 对象添加属性:基本属性之整形
    cJSON_AddItemToObject(jsonObject, "age", cJSON_CreateNumber(32));
    content = cJSON_Print(jsonObject);
    printf("%s\n",  content);
    
    err = pthread_create(&ntid, NULL, send_audio_thr_fn, NULL);
	if (err != 0)
		printf("can't create thread");

    printf("main thread\n");

    //初始化客户端
	ctx_info.port = CONTEXT_PORT_NO_LISTEN;
    ctx_info.iface = NULL;
    ctx_info.protocols = protocols;
    ctx_info.gid = (uid_t)-1;
    ctx_info.uid = (gid_t)-1;

    //设置ssl
    //client.set_ssl(NULL,NULL,NULL,0);
	set_ssl(NULL,NULL,NULL,0);

    //创建客户端
	// 创建一个WebSocket处理器
    context = lws_create_context( &ctx_info );
    if(!context)
        return -1;

    //连接服务器（需要ssl连接时传入1，否则传0）
    connect_server(0);

    //客户端运行
    while(n >= 0 && !interrupted)
        n = run_loop(1000);

    //销毁资源
    lws_context_destroy(context);

    return 0;
}